package org.rc.six;

import com.google.appengine.api.datastore.Blob;
import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.PreparedQuery;
import com.google.appengine.api.datastore.PreparedQuery.TooManyResultsException;
import com.google.appengine.api.datastore.Query;
import com.google.appengine.api.memcache.stdimpl.GCacheFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.Map;
import javax.cache.Cache;
import javax.cache.CacheException;
import javax.cache.CacheFactory;
import javax.cache.CacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author dokeeffe
 */
public class DataStoreIndexDao implements IndexDao {
    public static final String INDEX_CACHE_KEY_PREFIX = "index-";

    private static final Logger LOGGER = LoggerFactory.getLogger(DataStoreIndexDao.class);
    private static final int DEFAULT_IDX_STEM = 2;
    private Cache cache;
    private DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

    /**
     * Constructor ; sets up the cache
     */
    public DataStoreIndexDao() {
        Map props = new HashMap();
        props.put(GCacheFactory.EXPIRATION_DELTA, 3600);
        try {
            CacheFactory cacheFactory = CacheManager.getInstance().getCacheFactory();
            this.cache = cacheFactory.createCache(props);

        } catch (CacheException ex) {
            LOGGER.error("error creating cache",ex);
        }
    }

    @Override
    public Index getIndex(String term) {
        Index result = null;
        String stem = term.substring(0, DEFAULT_IDX_STEM);
        result = getIndexFromDatastore(stem);
        return result;
    }

    @Override
    public void storeIndex(Index ix) {
        try {
            Entity index = getDatastoreIndexEntity(ix.getStem());
            if(index==null) {
                index = new Entity("Index");
            }
            index.setProperty("stem", ix.getStem());
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutput out = new ObjectOutputStream(bos);
            out.writeObject(ix);
            out.close();
            byte[] bytes = bos.toByteArray();
            if(LOGGER.isDebugEnabled()) {
                LOGGER.debug("persisting index, size:" + bytes.length);
            }
            Blob blob = new Blob(bytes);
            index.setProperty("index", blob);
            datastore.put(index);
            cache.put(INDEX_CACHE_KEY_PREFIX + ix.getStem(), ix);
        } catch (IOException ex) {
            LOGGER.error("could not persist index",ex);
        }
    }

    private Index getIndexFromDatastore(String stem) {
        Index result = (Index) cache.get(INDEX_CACHE_KEY_PREFIX + stem);
        if (result == null) {
            Entity e = getDatastoreIndexEntity(stem);
            if(e!=null) {
                try {
                    Blob blob = (Blob) e.getProperty("index");
                    byte[] bytes = blob.getBytes();
                    ObjectInputStream is = new ObjectInputStream(new ByteArrayInputStream(bytes));
                    result = (Index) is.readObject();
                    is.close();
                } catch (Exception ex) {
                    LOGGER.error("error deserializing index");
                }
            } else {
                result = new Index(stem);
            }
            cache.put(INDEX_CACHE_KEY_PREFIX + stem, result);
        }
        return result;
    }

    /**
     * Gets the Index entity for a stem. Should only be 1
     * @param stem
     * @return
     * @throws com.google.appengine.api.datastore.PreparedQuery.TooManyResultsException
     */
    private Entity getDatastoreIndexEntity(String stem) throws TooManyResultsException {
        Query q = new Query("Index");
        q.addFilter("stem", Query.FilterOperator.EQUAL, stem);
        PreparedQuery pq = datastore.prepare(q);
        Entity e = pq.asSingleEntity();
        return e;
    }
}
